/*
 * 这是将 Double Metaphone 算法移植到 PostgreSQL 的版本。
 *
 * contrib/fuzzystrmatch/dmetaphone.c
 *
 * Double Metaphone 计算两个“发音相似”的字符串 - 一个主要的和一个
 * 备用的。在大多数情况下，它们是相同的，但对于外来名字
 * 尤其如此，它们可能会有些不同，取决于发音。
 *
 * 有关使用 Double Metaphone 的信息可以在
 *	 http://www.codeproject.com/string/dmetaphone1.asp
 * 找到，描述它的原始文章可以在
 *	 http://drdobbs.com/184401251
 *
 * 对于 PostgreSQL，我们提供两个函数 - 一个用于主要，另一个用于
 * 备用。这样函数就是纯文本->文本映射，在功能索引中非常有用。这些是用于
 * 主要的 'dmetaphone' 和用于备用的 'dmetaphone_alt'。
 *
 * 假设 dmetaphone.so 在 $libdir 中，设置
 * 这些函数的 SQL 看起来像这样：
 *
 * CREATE FUNCTION dmetaphone (text) RETURNS text
 *	  LANGUAGE C IMMUTABLE STRICT
 *	  AS '$libdir/dmetaphone', 'dmetaphone';
 *
 * CREATE FUNCTION dmetaphone_alt (text) RETURNS text
 *	  LANGUAGE C IMMUTABLE STRICT
 *	  AS '$libdir/dmetaphone', 'dmetaphone_alt';
 *
 * 注意，如果您想在功能索引中使用它们，则必须将函数声明为 IMMUTABLE，并且
 * 由于它们不检查 NULL 输入，因此必须将它们声明为 STRICT，如果给定 NULL 输入将导致
 * segfault。 （见下文的替代方案）将它们声明为 STRICT 意味着 PostgreSQL
 * 永远不会用 NULL 调用它们，而是假设结果是 NULL，这正是我们（我）想要的。
 *
 * 或者，可以使用 -DDMETAPHONE_NOSTRICT 编译，函数将检测 NULL 输入并返回 NULL。
 * 那样的话您就不必将它们声明为 STRICT。
 *
 * 这里有一个小的低效 - 每次函数调用实际上都会计算
 * 主要和备用，然后丢弃不需要的一个。这是 perl 模块编写的方式，因为 perl 可以
 * 更轻松地处理列表返回，而不是我们在 PostgreSQL 中。对于我的需求，结果
 * 已经足够快，但也许可以优化一下以消除
 * 这种行为。
 *
 */


/***************************** COPYRIGHT NOTICES ***********************

Most of this code is directly from the Text::DoubleMetaphone perl module
version 0.05 available from https://www.cpan.org/.
It bears this copyright notice:


  Copyright 2000, Maurice Aubrey <maurice@hevanet.com>.
  All rights reserved.

  This code is based heavily on the C++ implementation by
  Lawrence Philips and incorporates several bug fixes courtesy
  of Kevin Atkinson <kevina@users.sourceforge.net>.

  This module is free software; you may redistribute it and/or
  modify it under the same terms as Perl itself.

The remaining code is authored by Andrew Dunstan <amdunstan@ncshp.org> and
<andrew@dunslane.net> and is covered this copyright:

  Copyright 2003, North Carolina State Highway Patrol.
  All rights reserved.

  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose, without fee, and without a written agreement
  is hereby granted, provided that the above copyright notice and this
  paragraph and the following two paragraphs appear in all copies.

  IN NO EVENT SHALL THE NORTH CAROLINA STATE HIGHWAY PATROL BE LIABLE TO ANY
  PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
  INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
  DOCUMENTATION, EVEN IF THE NORTH CAROLINA STATE HIGHWAY PATROL HAS BEEN
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  THE NORTH CAROLINA STATE HIGHWAY PATROL SPECIFICALLY DISCLAIMS ANY
  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED
  HEREUNDER IS ON AN "AS IS" BASIS, AND THE NORTH CAROLINA STATE HIGHWAY PATROL
  HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
  MODIFICATIONS.

***********************************************************************/


/* 根据文档首先包含这些 */
#ifndef DMETAPHONE_MAIN

#include "postgres.h"

#include "utils/builtins.h"

/* 关闭嵌入函数的断言 */
#define NDEBUG

#else							/* DMETAPHONE_MAIN */

/* 如果我们没有从 postgres.h 中获得它们，我们需要这些 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#endif							/* DMETAPHONE_MAIN */

#include <assert.h>
#include <ctype.h>

/* 从 perl 模块获得的主函数的原型 */
static void fc_DoubleMetaphone(char *, char **);

#ifndef DMETAPHONE_MAIN

/*
 * PostgreSQL 可见的 dmetaphone 函数。
 */

PG_FUNCTION_INFO_V1(dmetaphone);

Datum dmetaphone(PG_FUNCTION_ARGS)
{
	text	   *fc_arg;
	char	   *fc_aptr,
			   *fc_codes[2],
			   *fc_code;

#ifdef DMETAPHONE_NOSTRICT
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
#endif
	fc_arg = PG_GETARG_TEXT_PP(0);
	fc_aptr = text_to_cstring(fc_arg);

	fc_DoubleMetaphone(fc_aptr, fc_codes);
	fc_code = fc_codes[0];
	if (!fc_code)
		fc_code = "";

	PG_RETURN_TEXT_P(cstring_to_text(fc_code));
}

/*
 * PostgreSQL 可见的 dmetaphone_alt 函数。
 */

PG_FUNCTION_INFO_V1(dmetaphone_alt);

Datum dmetaphone_alt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg;
	char	   *fc_aptr,
			   *fc_codes[2],
			   *fc_code;

#ifdef DMETAPHONE_NOSTRICT
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
#endif
	fc_arg = PG_GETARG_TEXT_PP(0);
	fc_aptr = text_to_cstring(fc_arg);

	fc_DoubleMetaphone(fc_aptr, fc_codes);
	fc_code = fc_codes[1];
	if (!fc_code)
		fc_code = "";

	PG_RETURN_TEXT_P(cstring_to_text(fc_code));
}


/* 这里是开始从 perl 模块导入的代码 */

/* 所有内存处理都是使用这些宏完成的 */

#define META_MALLOC(v,n,t) \
		  (v = (t*)palloc(((n)*sizeof(t))))

#define META_REALLOC(v,n,t) \
					  (v = (t*)repalloc((v),((n)*sizeof(t))))

/*
 * 不要做 pfree - 这似乎有时会导致 SIGSEGV - 这可能只是
 * 因为在开发中重新加载模块造成的。
 * 所以我们依赖上下文清理 - Tom Lane 说在这种情况下
 * 不需要 pfree。
 */

#define META_FREE(x) ((void)true)	/* pfree((x)) */
#else							/* 未定义 DMETAPHONE_MAIN */

/* 当不在 PostgreSQL 中运行时使用标准的 malloc 库 */

#define META_MALLOC(v,n,t) \
		  (v = (t*)malloc(((n)*sizeof(t))))

#define META_REALLOC(v,n,t) \
					  (v = (t*)realloc((v),((n)*sizeof(t))))

#define META_FREE(x) free((x))
#endif							/* 定义了 DMETAPHONE_MAIN */



/* 这个 typedef 最初在 perl 模块的 .h 文件中 */

typedef struct
{
	char	   *str;
	int			length;
	int			bufsize;
	int			free_string_on_destroy;
}

metastring;

/*
 * 其余 perl 模块函数未更改，除了将它们声明为静态
 * 以及重新格式化为 PostgreSQL 的缩进，并适合 80 列。
 *
 */

static metastring *
fc_NewMetaString(const char *fc_init_str)
{
	metastring *fc_s;
	char		fc_empty_string[] = "";

	META_MALLOC(fc_s, 1, metastring);
	assert(s != NULL);

	if (fc_init_str == NULL)
		fc_init_str = fc_empty_string;
	fc_s->length = strlen(fc_init_str);
	/* 预分配更多空间以应对潜在的增长 */
	fc_s->bufsize = fc_s->length + 7;

	META_MALLOC(fc_s->str, fc_s->bufsize, char);
	assert(s->str != NULL);

	memcpy(fc_s->str, fc_init_str, fc_s->length + 1);
	fc_s->free_string_on_destroy = 1;

	return fc_s;
}


static void fc_DestroyMetaString(metastring *fc_s)
{
	if (fc_s == NULL)
		return;

	if (fc_s->free_string_on_destroy && (fc_s->str != NULL))
		META_FREE(s->str);

	META_FREE(s);
}


static void fc_IncreaseBuffer(metastring *fc_s, int fc_chars_needed)
{
	META_REALLOC(fc_s->str, (fc_s->bufsize + fc_chars_needed + 10), char);
	assert(s->str != NULL);
	fc_s->bufsize = fc_s->bufsize + fc_chars_needed + 10;
}


static void fc_MakeUpper(metastring *fc_s)
{
	char	   *fc_i;

	for (fc_i = fc_s->str; *fc_i; fc_i++)
		*fc_i = toupper((unsigned char) *fc_i);
}


static int fc_IsVowel(metastring *fc_s, int fc_pos)
{
	char		fc_c;

	if ((fc_pos < 0) || (fc_pos >= fc_s->length))
		return 0;

	fc_c = *(fc_s->str + fc_pos);
	if ((fc_c == 'A') || (fc_c == 'E') || (fc_c == 'I') || (fc_c == 'O') ||
		(fc_c == 'U') || (fc_c == 'Y'))
		return 1;

	return 0;
}


static int fc_SlavoGermanic(metastring *fc_s)
{
	if ((char *) strstr(fc_s->str, "W"))
		return 1;
	else if ((char *) strstr(fc_s->str, "K"))
		return 1;
	else if ((char *) strstr(fc_s->str, "CZ"))
		return 1;
	else if ((char *) strstr(fc_s->str, "WITZ"))
		return 1;
	else
		return 0;
}


static char fc_GetAt(metastring *fc_s, int fc_pos)
{
	if ((fc_pos < 0) || (fc_pos >= fc_s->length))
		return '\0';

	return ((char) *(fc_s->str + fc_pos));
}


static void fc_SetAt(metastring *fc_s, int fc_pos, char fc_c)
{
	if ((fc_pos < 0) || (fc_pos >= fc_s->length))
		return;

	*(fc_s->str + fc_pos) = fc_c;
}


/*
   警告：START 值是基于 0 的
*/
static int fc_StringAt(metastring *fc_s, int fc_start, int fc_length,...)
{
	char	   *fc_test;
	char	   *fc_pos;
	va_list		fc_ap;

	if ((fc_start < 0) || (fc_start >= fc_s->length))
		return 0;

	fc_pos = (fc_s->str + fc_start);
	va_start(fc_ap, fc_length);

	do
	{
		fc_test = va_arg(fc_ap, char *);
		if (*fc_test && (strncmp(fc_pos, fc_test, fc_length) == 0))
		{
			va_end(fc_ap);
			return 1;
		}
	}
	while (strcmp(fc_test, "") != 0);

	va_end(fc_ap);

	return 0;
}


static void fc_MetaphAdd(metastring *fc_s, const char *fc_new_str)
{
	int			fc_add_length;

	if (fc_new_str == NULL)
		return;

	fc_add_length = strlen(fc_new_str);
	if ((fc_s->length + fc_add_length) > (fc_s->bufsize - 1))
		fc_IncreaseBuffer(fc_s, fc_add_length);

	strcat(fc_s->str, fc_new_str);
	fc_s->length += fc_add_length;
}


static void fc_DoubleMetaphone(char *fc_str, char **fc_codes)
{
	int			fc_length;
	metastring *fc_original;
	metastring *fc_primary;
	metastring *fc_secondary;
	int			fc_current;
	int			fc_last;

	fc_current = 0;
	/* 我们需要真实长度和最后长度以便填充 */
	fc_length = strlen(fc_str);
	fc_last = fc_length - 1;
	fc_original = fc_NewMetaString(fc_str);
	/* 填充原始数据以便我们可以索引超出末尾 */
	fc_MetaphAdd(fc_original, "     ");

	fc_primary = fc_NewMetaString("");
	fc_secondary = fc_NewMetaString("");
	fc_primary->free_string_on_destroy = 0;
	fc_secondary->free_string_on_destroy = 0;

	fc_MakeUpper(fc_original);

	/* 在单词开头时跳过这些 */
	if (fc_StringAt(fc_original, 0, 2, "GN", "KN", "PN", "WR", "PS", ""))
		fc_current += 1;

	/* 初始的 'X' 发音为 'Z' 例如 'Xavier' */
	if (fc_GetAt(fc_original, 0) == 'X')
	{
		fc_MetaphAdd(fc_primary, "S");	/* 'Z' 对应 'S' */
		fc_MetaphAdd(fc_secondary, "S");
		fc_current += 1;
	}

	/* 主循环 */
	while ((fc_primary->length < 4) || (fc_secondary->length < 4))
	{
		if (fc_current >= fc_length)
			break;

		switch (fc_GetAt(fc_original, fc_current))
		{
			case 'A':
			case 'E':
			case 'I':
			case 'O':
			case 'U':
			case 'Y':
				if (fc_current == 0)
				{
					/* 所有初始化元音现在映射到 'A' */
					fc_MetaphAdd(fc_primary, "A");
					fc_MetaphAdd(fc_secondary, "A");
				}
				fc_current += 1;
				break;

			case 'B':

				/* "-mb"，例如 "dumb"，已经跳过... */
				fc_MetaphAdd(fc_primary, "P");
				fc_MetaphAdd(fc_secondary, "P");

				if (fc_GetAt(fc_original, fc_current + 1) == 'B')
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case '\xc7':		/* 带有变音符的 C */
				fc_MetaphAdd(fc_primary, "S");
				fc_MetaphAdd(fc_secondary, "S");
				fc_current += 1;
				break;

			case 'C':
				/* 各种日耳曼语 */
				if ((fc_current > 1)
					&& !fc_IsVowel(fc_original, fc_current - 2)
					&& fc_StringAt(fc_original, (fc_current - 1), 3, "ACH", "")
					&& ((fc_GetAt(fc_original, fc_current + 2) != 'I')
						&& ((fc_GetAt(fc_original, fc_current + 2) != 'E')
							|| fc_StringAt(fc_original, (fc_current - 2), 6, "BACHER",
										"MACHER", ""))))
				{
					fc_MetaphAdd(fc_primary, "K");
					fc_MetaphAdd(fc_secondary, "K");
					fc_current += 2;
					break;
				}

				/* 特殊情况 'caesar' */
				if ((fc_current == 0)
					&& fc_StringAt(fc_original, fc_current, 6, "CAESAR", ""))
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "S");
					fc_current += 2;
					break;
				}

				/* 意大利语 'chianti' */
				if (fc_StringAt(fc_original, fc_current, 4, "CHIA", ""))
				{
					fc_MetaphAdd(fc_primary, "K");
					fc_MetaphAdd(fc_secondary, "K");
					fc_current += 2;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 2, "CH", ""))
				{
					/* 查找 'michael' */
					if ((fc_current > 0)
						&& fc_StringAt(fc_original, fc_current, 4, "CHAE", ""))
					{
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "X");
						fc_current += 2;
						break;
					}

					/* 希腊根词，例如 'chemistry', 'chorus' */
					if ((fc_current == 0)
						&& (fc_StringAt(fc_original, (fc_current + 1), 5,
									 "HARAC", "HARIS", "")
							|| fc_StringAt(fc_original, (fc_current + 1), 3, "HOR",
										"HYM", "HIA", "HEM", ""))
						&& !fc_StringAt(fc_original, 0, 5, "CHORE", ""))
					{
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "K");
						fc_current += 2;
						break;
					}

					/* 日耳曼语、希腊语或其他情况下 'ch' 为 'kh' 音 */
					if ((fc_StringAt(fc_original, 0, 4, "VAN ", "VON ", "")
						 || fc_StringAt(fc_original, 0, 3, "SCH", ""))
					/* 'architect' 但不是 'arch'，'orchestra'，'orchid' */
						|| fc_StringAt(fc_original, (fc_current - 2), 6, "ORCHES",
									"ARCHIT", "ORCHID", "")
						|| fc_StringAt(fc_original, (fc_current + 2), 1, "T", "S",
									"")
						|| ((fc_StringAt(fc_original, (fc_current - 1), 1,
									  "A", "O", "U", "E", "")
							 || (fc_current == 0))

					/*
					 * 例如 'wachtler'，'wechsler'，但不是 'tichner'
					 */
							&& fc_StringAt(fc_original, (fc_current + 2), 1, "L", "R",
										"N", "M", "B", "H", "F", "V", "W",
										" ", "")))
					{
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "K");
					}
					else
					{
						if (fc_current > 0)
						{
							if (fc_StringAt(fc_original, 0, 2, "MC", ""))
							{
								/* 例如 "McHugh" */
								fc_MetaphAdd(fc_primary, "K");
								fc_MetaphAdd(fc_secondary, "K");
							}
							else
							{
								fc_MetaphAdd(fc_primary, "X");
								fc_MetaphAdd(fc_secondary, "K");
							}
						}
						else
						{
							fc_MetaphAdd(fc_primary, "X");
							fc_MetaphAdd(fc_secondary, "X");
						}
					}
					fc_current += 2;
					break;
				}
				/* 例如 'czerny' */
				if (fc_StringAt(fc_original, fc_current, 2, "CZ", "")
					&& !fc_StringAt(fc_original, (fc_current - 2), 4, "WICZ", ""))
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "X");
					fc_current += 2;
					break;
				}

				/* 例如 'focaccia' */
				if (fc_StringAt(fc_original, (fc_current + 1), 3, "CIA", ""))
				{
					fc_MetaphAdd(fc_primary, "X");
					fc_MetaphAdd(fc_secondary, "X");
					fc_current += 3;
					break;
				}

				/* 双 'C'，但如果例如 'McClellan' 就不算 */
				if (fc_StringAt(fc_original, fc_current, 2, "CC", "")
					&& !((fc_current == 1) && (fc_GetAt(fc_original, 0) == 'M')))
				{
					/* 'bellocchio' 但不 'bacchus' */
					if (fc_StringAt(fc_original, (fc_current + 2), 1, "I", "E", "H", "")
						&& !fc_StringAt(fc_original, (fc_current + 2), 2, "HU", ""))
					{
						/* 'accident'，'accede'，'succeed' */
						if (((fc_current == 1)
							 && (fc_GetAt(fc_original, fc_current - 1) == 'A'))
							|| fc_StringAt(fc_original, (fc_current - 1), 5, "UCCEE",
										"UCCES", ""))
						{
							fc_MetaphAdd(fc_primary, "KS");
							fc_MetaphAdd(fc_secondary, "KS");
							/* 'bacci'，'bertucci'，其他意大利语 */
						}
						else
						{
							fc_MetaphAdd(fc_primary, "X");
							fc_MetaphAdd(fc_secondary, "X");
						}
						fc_current += 3;
						break;
					}
					else
					{			/* 皮尔斯规则 */
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "K");
						fc_current += 2;
						break;
					}
				}

				if (fc_StringAt(fc_original, fc_current, 2, "CK", "CG", "CQ", ""))
				{
					fc_MetaphAdd(fc_primary, "K");
					fc_MetaphAdd(fc_secondary, "K");
					fc_current += 2;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 2, "CI", "CE", "CY", ""))
				{
					/* 意大利语与英语 */
					if (fc_StringAt
						(fc_original, fc_current, 3, "CIO", "CIE", "CIA", ""))
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "X");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "S");
					}
					fc_current += 2;
					break;
				}

				/* 其他 */
				fc_MetaphAdd(fc_primary, "K");
				fc_MetaphAdd(fc_secondary, "K");

				/* 名字，例如 'mac caffrey', 'mac gregor' */
				if (fc_StringAt(fc_original, (fc_current + 1), 2, " C", " Q", " G", ""))
					fc_current += 3;
				else if (fc_StringAt(fc_original, (fc_current + 1), 1, "C", "K", "Q", "")
						 && !fc_StringAt(fc_original, (fc_current + 1), 2,
									  "CE", "CI", ""))
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case 'D':
				if (fc_StringAt(fc_original, fc_current, 2, "DG", ""))
				{
					if (fc_StringAt(fc_original, (fc_current + 2), 1,
								 "I", "E", "Y", ""))
					{
						/* 例如 'edge' */
						fc_MetaphAdd(fc_primary, "J");
						fc_MetaphAdd(fc_secondary, "J");
						fc_current += 3;
						break;
					}
					else
					{
						/* 例如 'edgar' */
						fc_MetaphAdd(fc_primary, "TK");
						fc_MetaphAdd(fc_secondary, "TK");
						fc_current += 2;
						break;
					}
				}

				if (fc_StringAt(fc_original, fc_current, 2, "DT", "DD", ""))
				{
					fc_MetaphAdd(fc_primary, "T");
					fc_MetaphAdd(fc_secondary, "T");
					fc_current += 2;
					break;
				}

				/* 其他 */
				fc_MetaphAdd(fc_primary, "T");
				fc_MetaphAdd(fc_secondary, "T");
				fc_current += 1;
				break;

			case 'F':
				if (fc_GetAt(fc_original, fc_current + 1) == 'F')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "F");
				fc_MetaphAdd(fc_secondary, "F");
				break;

			case 'G':
				if (fc_GetAt(fc_original, fc_current + 1) == 'H')
				{
					if ((fc_current > 0) && !fc_IsVowel(fc_original, fc_current - 1))
					{
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "K");
						fc_current += 2;
						break;
					}

					if (fc_current < 3)
					{
						/* 'ghislane'，'ghiradelli' */
						if (fc_current == 0)
						{
							if (fc_GetAt(fc_original, fc_current + 2) == 'I')
							{
								fc_MetaphAdd(fc_primary, "J");
								fc_MetaphAdd(fc_secondary, "J");
							}
							else
							{
								fc_MetaphAdd(fc_primary, "K");
								fc_MetaphAdd(fc_secondary, "K");
							}
							fc_current += 2;
							break;
						}
					}

					/*
					 * 帕克规则（经过一些进一步的完善） - 例如，
					 * 'hugh'
					 */
					if (((fc_current > 1)
						 && fc_StringAt(fc_original, (fc_current - 2), 1,
									 "B", "H", "D", ""))
					/* 例如 'bough' */
						|| ((fc_current > 2)
							&& fc_StringAt(fc_original, (fc_current - 3), 1,
										"B", "H", "D", ""))
					/* 例如 'broughton' */
						|| ((fc_current > 3)
							&& fc_StringAt(fc_original, (fc_current - 4), 1,
										"B", "H", "")))
					{
						fc_current += 2;
						break;
					}
					else
					{
						/*
						 * 例如 'laugh'，'McLaughlin'，'cough'，'gough'，
						 * 'rough'，'tough'
						 */
						if ((fc_current > 2)
							&& (fc_GetAt(fc_original, fc_current - 1) == 'U')
							&& fc_StringAt(fc_original, (fc_current - 3), 1, "C",
										"G", "L", "R", "T", ""))
						{
							fc_MetaphAdd(fc_primary, "F");
							fc_MetaphAdd(fc_secondary, "F");
						}
						else if ((fc_current > 0)
								 && fc_GetAt(fc_original, fc_current - 1) != 'I')
						{


							fc_MetaphAdd(fc_primary, "K");
							fc_MetaphAdd(fc_secondary, "K");
						}

						fc_current += 2;
						break;
					}
				}

				if (fc_GetAt(fc_original, fc_current + 1) == 'N')
				{
					if ((fc_current == 1) && fc_IsVowel(fc_original, 0)
						&& !fc_SlavoGermanic(fc_original))
					{
						fc_MetaphAdd(fc_primary, "KN");
						fc_MetaphAdd(fc_secondary, "N");
					}
					else
						/* 不是例如 'cagney' */
						if (!fc_StringAt(fc_original, (fc_current + 2), 2, "EY", "")
							&& (fc_GetAt(fc_original, fc_current + 1) != 'Y')
							&& !fc_SlavoGermanic(fc_original))
					{
						fc_MetaphAdd(fc_primary, "N");
						fc_MetaphAdd(fc_secondary, "KN");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "KN");
						fc_MetaphAdd(fc_secondary, "KN");
					}
					fc_current += 2;
					break;
				}

				/* 'tagliaro' */
				if (fc_StringAt(fc_original, (fc_current + 1), 2, "LI", "")
					&& !fc_SlavoGermanic(fc_original))
				{
					fc_MetaphAdd(fc_primary, "KL");
					fc_MetaphAdd(fc_secondary, "L");
					fc_current += 2;
					break;
				}

				/* -ges-，-gep-，-gel-，-gie- 在开头 */
				if ((fc_current == 0)
					&& ((fc_GetAt(fc_original, fc_current + 1) == 'Y')
						|| fc_StringAt(fc_original, (fc_current + 1), 2, "ES", "EP",
									"EB", "EL", "EY", "IB", "IL", "IN", "IE",
									"EI", "ER", "")))
				{
					fc_MetaphAdd(fc_primary, "K");
					fc_MetaphAdd(fc_secondary, "J");
					fc_current += 2;
					break;
				}

				/* -ger-， -gy- */
				if ((fc_StringAt(fc_original, (fc_current + 1), 2, "ER", "")
					 || (fc_GetAt(fc_original, fc_current + 1) == 'Y'))
					&& !fc_StringAt(fc_original, 0, 6,
								 "DANGER", "RANGER", "MANGER", "")
					&& !fc_StringAt(fc_original, (fc_current - 1), 1, "E", "I", "")
					&& !fc_StringAt(fc_original, (fc_current - 1), 3, "RGY", "OGY", ""))
				{
					fc_MetaphAdd(fc_primary, "K");
					fc_MetaphAdd(fc_secondary, "J");
					fc_current += 2;
					break;
				}

				/* 意大利语，例如 'biaggi' */
				if (fc_StringAt(fc_original, (fc_current + 1), 1, "E", "I", "Y", "")
					|| fc_StringAt(fc_original, (fc_current - 1), 4,
								"AGGI", "OGGI", ""))
				{
					/* 显而易见的日耳曼语 */
					if ((fc_StringAt(fc_original, 0, 4, "VAN ", "VON ", "")
						 || fc_StringAt(fc_original, 0, 3, "SCH", ""))
						|| fc_StringAt(fc_original, (fc_current + 1), 2, "ET", ""))
					{
						fc_MetaphAdd(fc_primary, "K");
						fc_MetaphAdd(fc_secondary, "K");
					}
					else
					{
						/* 如果是法语结尾，始终发音柔和 */
						if (fc_StringAt
							(fc_original, (fc_current + 1), 4, "IER ", ""))
						{
							fc_MetaphAdd(fc_primary, "J");
							fc_MetaphAdd(fc_secondary, "J");
						}
						else
						{
							fc_MetaphAdd(fc_primary, "J");
							fc_MetaphAdd(fc_secondary, "K");
						}
					}
					fc_current += 2;
					break;
				}

				if (fc_GetAt(fc_original, fc_current + 1) == 'G')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "K");
				fc_MetaphAdd(fc_secondary, "K");
				break;

			case 'H':
				/* 仅当位于元音前或在两个元音之间时保留 */
				if (((fc_current == 0) || fc_IsVowel(fc_original, fc_current - 1))
					&& fc_IsVowel(fc_original, fc_current + 1))
				{
					fc_MetaphAdd(fc_primary, "H");
					fc_MetaphAdd(fc_secondary, "H");
					fc_current += 2;
				}
				else
					/* 也处理 'HH' */
					fc_current += 1;
				break;

			case 'J':
				/* 显而易见的西班牙语，例如 'jose'，'san jacinto' */
				if (fc_StringAt(fc_original, fc_current, 4, "JOSE", "")
					|| fc_StringAt(fc_original, 0, 4, "SAN ", ""))
				{
					if (((fc_current == 0)
						 && (fc_GetAt(fc_original, fc_current + 4) == ' '))
						|| fc_StringAt(fc_original, 0, 4, "SAN ", ""))
					{
						fc_MetaphAdd(fc_primary, "H");
						fc_MetaphAdd(fc_secondary, "H");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "J");
						fc_MetaphAdd(fc_secondary, "H");
					}
					fc_current += 1;
					break;
				}

				if ((fc_current == 0)
					&& !fc_StringAt(fc_original, fc_current, 4, "JOSE", ""))
				{
					fc_MetaphAdd(fc_primary, "J");	/* Yankelovich/Jankelowicz */
					fc_MetaphAdd(fc_secondary, "A");
				}
				else
				{
					/* 西班牙语发音，例如 'bajador' */
					if (fc_IsVowel(fc_original, fc_current - 1)
						&& !fc_SlavoGermanic(fc_original)
						&& ((fc_GetAt(fc_original, fc_current + 1) == 'A')
							|| (fc_GetAt(fc_original, fc_current + 1) == 'O')))
					{
						fc_MetaphAdd(fc_primary, "J");
						fc_MetaphAdd(fc_secondary, "H");
					}
					else
					{
						if (fc_current == fc_last)
						{
							fc_MetaphAdd(fc_primary, "J");
							fc_MetaphAdd(fc_secondary, "");
						}
						else
						{
							if (!fc_StringAt(fc_original, (fc_current + 1), 1, "L", "T",
										  "K", "S", "N", "M", "B", "Z", "")
								&& !fc_StringAt(fc_original, (fc_current - 1), 1,
											 "S", "K", "L", ""))
							{
								fc_MetaphAdd(fc_primary, "J");
								fc_MetaphAdd(fc_secondary, "J");
							}
						}
					}
				}

				if (fc_GetAt(fc_original, fc_current + 1) == 'J')	/* 这可能会发生！ */
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case 'K':
				if (fc_GetAt(fc_original, fc_current + 1) == 'K')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "K");
				fc_MetaphAdd(fc_secondary, "K");
				break;

			case 'L':
				if (fc_GetAt(fc_original, fc_current + 1) == 'L')
				{
					/* 西班牙语，例如 'cabrillo'，'gallegos' */
					if (((fc_current == (fc_length - 3))
						 && fc_StringAt(fc_original, (fc_current - 1), 4, "ILLO",
									 "ILLA", "ALLE", ""))
						|| ((fc_StringAt(fc_original, (fc_last - 1), 2, "AS", "OS", "")
							 || fc_StringAt(fc_original, fc_last, 1, "A", "O", ""))
							&& fc_StringAt(fc_original, (fc_current - 1), 4,
										"ALLE", "")))
					{
						fc_MetaphAdd(fc_primary, "L");
						fc_MetaphAdd(fc_secondary, "");
						fc_current += 2;
						break;
					}
					fc_current += 2;
				}
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "L");
				fc_MetaphAdd(fc_secondary, "L");
				break;

			case 'M':
				if ((fc_StringAt(fc_original, (fc_current - 1), 3, "UMB", "")
					 && (((fc_current + 1) == fc_last)
						 || fc_StringAt(fc_original, (fc_current + 2), 2, "ER", "")))
				/* 'dumb','thumb' */
					|| (fc_GetAt(fc_original, fc_current + 1) == 'M'))
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "M");
				fc_MetaphAdd(fc_secondary, "M");
				break;

			case 'N':
				if (fc_GetAt(fc_original, fc_current + 1) == 'N')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "N");
				fc_MetaphAdd(fc_secondary, "N");
				break;

			case '\xd1':		/* 带波浪符的 N */
				fc_current += 1;
				fc_MetaphAdd(fc_primary, "N");
				fc_MetaphAdd(fc_secondary, "N");
				break;

			case 'P':
				if (fc_GetAt(fc_original, fc_current + 1) == 'H')
				{
					fc_MetaphAdd(fc_primary, "F");
					fc_MetaphAdd(fc_secondary, "F");
					fc_current += 2;
					break;
				}

				/* 还要考虑 "campbell"，"raspberry" */
				if (fc_StringAt(fc_original, (fc_current + 1), 1, "P", "B", ""))
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "P");
				fc_MetaphAdd(fc_secondary, "P");
				break;

			case 'Q':
				if (fc_GetAt(fc_original, fc_current + 1) == 'Q')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "K");
				fc_MetaphAdd(fc_secondary, "K");
				break;

			case 'R':
				/* 法语，例如 'rogier'，但排除 'hochmeier' */
				if ((fc_current == fc_last)
					&& !fc_SlavoGermanic(fc_original)
					&& fc_StringAt(fc_original, (fc_current - 2), 2, "IE", "")
					&& !fc_StringAt(fc_original, (fc_current - 4), 2, "ME", "MA", ""))
				{
					fc_MetaphAdd(fc_primary, "");
					fc_MetaphAdd(fc_secondary, "R");
				}
				else
				{
					fc_MetaphAdd(fc_primary, "R");
					fc_MetaphAdd(fc_secondary, "R");
				}

				if (fc_GetAt(fc_original, fc_current + 1) == 'R')
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case 'S':
				/* 特殊情况 'island'，'isle'，'carlisle'，'carlysle' */
				if (fc_StringAt(fc_original, (fc_current - 1), 3, "ISL", "YSL", ""))
				{
					fc_current += 1;
					break;
				}

				/* 特殊情况 'sugar-' */
				if ((fc_current == 0)
					&& fc_StringAt(fc_original, fc_current, 5, "SUGAR", ""))
				{
					fc_MetaphAdd(fc_primary, "X");
					fc_MetaphAdd(fc_secondary, "S");
					fc_current += 1;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 2, "SH", ""))
				{
					/* 日耳曼语 */
					if (fc_StringAt
						(fc_original, (fc_current + 1), 4, "HEIM", "HOEK", "HOLM",
						 "HOLZ", ""))
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "S");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "X");
						fc_MetaphAdd(fc_secondary, "X");
					}
					fc_current += 2;
					break;
				}

				/* 意大利语和亚美尼亚语 */
				if (fc_StringAt(fc_original, fc_current, 3, "SIO", "SIA", "")
					|| fc_StringAt(fc_original, fc_current, 4, "SIAN", ""))
				{
					if (!fc_SlavoGermanic(fc_original))
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "X");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "S");
					}
					fc_current += 3;
					break;
				}

				/*
				 * 德语和英语化，例如 'smith' 对应 'schmidt'，
				 * 'snider' 对应 'schneider'，同时，斯拉夫语言中的 -sz- 
				 * 虽然在匈牙利语中发音为 's'
				 */
				if (((fc_current == 0)
					 && fc_StringAt(fc_original, (fc_current + 1), 1,
								 "M", "N", "L", "W", ""))
					|| fc_StringAt(fc_original, (fc_current + 1), 1, "Z", ""))
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "X");
					if (fc_StringAt(fc_original, (fc_current + 1), 1, "Z", ""))
						fc_current += 2;
					else
						fc_current += 1;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 2, "SC", ""))
				{
					/* Schlesinger 的规则 */
					if (fc_GetAt(fc_original, fc_current + 2) == 'H')
					{
						/* 荷兰来源，例如 'school', 'schooner' */
						if (fc_StringAt(fc_original, (fc_current + 3), 2,
									 "OO", "ER", "EN",
									 "UY", "ED", "EM", ""))
						{
							/* 'schermerhorn', 'schenker' */
							if (fc_StringAt(fc_original, (fc_current + 3), 2,
										 "ER", "EN", ""))
							{
								fc_MetaphAdd(fc_primary, "X");
								fc_MetaphAdd(fc_secondary, "SK");
							}
							else
							{
								fc_MetaphAdd(fc_primary, "SK");
								fc_MetaphAdd(fc_secondary, "SK");
							}
							fc_current += 3;
							break;
						}
						else
						{
							if ((fc_current == 0) && !fc_IsVowel(fc_original, 3)
								&& (fc_GetAt(fc_original, 3) != 'W'))
							{
								fc_MetaphAdd(fc_primary, "X");
								fc_MetaphAdd(fc_secondary, "S");
							}
							else
							{
								fc_MetaphAdd(fc_primary, "X");
								fc_MetaphAdd(fc_secondary, "X");
							}
							fc_current += 3;
							break;
						}
					}

					if (fc_StringAt(fc_original, (fc_current + 2), 1,
								 "I", "E", "Y", ""))
					{
						fc_MetaphAdd(fc_primary, "S");
						fc_MetaphAdd(fc_secondary, "S");
						fc_current += 3;
						break;
					}
					/* 其他 */
					fc_MetaphAdd(fc_primary, "SK");
					fc_MetaphAdd(fc_secondary, "SK");
					fc_current += 3;
					break;
				}

				/* 法语，例如 'resnais', 'artois' */
				if ((fc_current == fc_last)
					&& fc_StringAt(fc_original, (fc_current - 2), 2, "AI", "OI", ""))
				{
					fc_MetaphAdd(fc_primary, "");
					fc_MetaphAdd(fc_secondary, "S");
				}
				else
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "S");
				}

				if (fc_StringAt(fc_original, (fc_current + 1), 1, "S", "Z", ""))
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case 'T':
				if (fc_StringAt(fc_original, fc_current, 4, "TION", ""))
				{
					fc_MetaphAdd(fc_primary, "X");
					fc_MetaphAdd(fc_secondary, "X");
					fc_current += 3;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 3, "TIA", "TCH", ""))
				{
					fc_MetaphAdd(fc_primary, "X");
					fc_MetaphAdd(fc_secondary, "X");
					fc_current += 3;
					break;
				}

				if (fc_StringAt(fc_original, fc_current, 2, "TH", "")
					|| fc_StringAt(fc_original, fc_current, 3, "TTH", ""))
				{
					/* 特殊情况 'thomas', 'thames' 或日耳曼语 */
					if (fc_StringAt(fc_original, (fc_current + 2), 2, "OM", "AM", "")
						|| fc_StringAt(fc_original, 0, 4, "VAN ", "VON ", "")
						|| fc_StringAt(fc_original, 0, 3, "SCH", ""))
					{
						fc_MetaphAdd(fc_primary, "T");
						fc_MetaphAdd(fc_secondary, "T");
					}
					else
					{
						fc_MetaphAdd(fc_primary, "0");
						fc_MetaphAdd(fc_secondary, "T");
					}
					fc_current += 2;
					break;
				}

				if (fc_StringAt(fc_original, (fc_current + 1), 1, "T", "D", ""))
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "T");
				fc_MetaphAdd(fc_secondary, "T");
				break;

			case 'V':
				if (fc_GetAt(fc_original, fc_current + 1) == 'V')
					fc_current += 2;
				else
					fc_current += 1;
				fc_MetaphAdd(fc_primary, "F");
				fc_MetaphAdd(fc_secondary, "F");
				break;

			case 'W':
				/* 也可以在单词的中间 */
				if (fc_StringAt(fc_original, fc_current, 2, "WR", ""))
				{
					fc_MetaphAdd(fc_primary, "R");
					fc_MetaphAdd(fc_secondary, "R");
					fc_current += 2;
					break;
				}

				if ((fc_current == 0)
					&& (fc_IsVowel(fc_original, fc_current + 1)
						|| fc_StringAt(fc_original, fc_current, 2, "WH", "")))
				{
					/* Wasserman 应匹配 Vasserman */
					if (fc_IsVowel(fc_original, fc_current + 1))
					{
						fc_MetaphAdd(fc_primary, "A");
						fc_MetaphAdd(fc_secondary, "F");
					}
					else
					{
						/* 需要 Uomo 匹配 Womo */
						fc_MetaphAdd(fc_primary, "A");
						fc_MetaphAdd(fc_secondary, "A");
					}
				}

				/* Arnow 应匹配 Arnoff */
				if (((fc_current == fc_last) && fc_IsVowel(fc_original, fc_current - 1))
					|| fc_StringAt(fc_original, (fc_current - 1), 5, "EWSKI", "EWSKY",
								"OWSKI", "OWSKY", "")
					|| fc_StringAt(fc_original, 0, 3, "SCH", ""))
				{
					fc_MetaphAdd(fc_primary, "");
					fc_MetaphAdd(fc_secondary, "F");
					fc_current += 1;
					break;
				}

				/* 波兰语，例如 'filipowicz' */
				if (fc_StringAt(fc_original, fc_current, 4, "WICZ", "WITZ", ""))
				{
					fc_MetaphAdd(fc_primary, "TS");
					fc_MetaphAdd(fc_secondary, "FX");
					fc_current += 4;
					break;
				}

				/* 否则跳过 */
				fc_current += 1;
				break;

			case 'X':
				/* 法语，例如 breaux */
				if (!((fc_current == fc_last)
					  && (fc_StringAt(fc_original, (fc_current - 3), 3,
								   "IAU", "EAU", "")
						  || fc_StringAt(fc_original, (fc_current - 2), 2,
									  "AU", "OU", ""))))
				{
					fc_MetaphAdd(fc_primary, "KS");
					fc_MetaphAdd(fc_secondary, "KS");
				}


				if (fc_StringAt(fc_original, (fc_current + 1), 1, "C", "X", ""))
					fc_current += 2;
				else
					fc_current += 1;
				break;

			case 'Z':
				/* 中文拼音，例如 'zhao' */
				if (fc_GetAt(fc_original, fc_current + 1) == 'H')
				{
					fc_MetaphAdd(fc_primary, "J");
					fc_MetaphAdd(fc_secondary, "J");
					fc_current += 2;
					break;
				}
				else if (fc_StringAt(fc_original, (fc_current + 1), 2,
								  "ZO", "ZI", "ZA", "")
						 || (fc_SlavoGermanic(fc_original)
							 && ((fc_current > 0)
								 && fc_GetAt(fc_original, fc_current - 1) != 'T')))
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "TS");
				}
				else
				{
					fc_MetaphAdd(fc_primary, "S");
					fc_MetaphAdd(fc_secondary, "S");
				}

				if (fc_GetAt(fc_original, fc_current + 1) == 'Z')
					fc_current += 2;
				else
					fc_current += 1;
				break;

			default:
				fc_current += 1;
		}

		/*
		 * printf("PRIMARY: %s\n", primary->str); printf("SECONDARY: %s\n",
		 * secondary->str);
		 */
	}


	if (fc_primary->length > 4)
		fc_SetAt(fc_primary, 4, '\0');

	if (fc_secondary->length > 4)
		fc_SetAt(fc_secondary, 4, '\0');

	*fc_codes = fc_primary->str;
	*++fc_codes = fc_secondary->str;

	fc_DestroyMetaString(fc_original);
	fc_DestroyMetaString(fc_primary);
	fc_DestroyMetaString(fc_secondary);
}

#ifdef DMETAPHONE_MAIN

/* 只是为了测试 - 不是 perl 代码的一部分 */

main(int argc, char **argv)
{
	char	   *fc_codes[2];

	if (argc > 1)
	{
		DoubleMetaphone(argv[1], fc_codes);
		printf("%s|%s\n", fc_codes[0], fc_codes[1]);
	}
}

#endif
